// $Id: config_file.cpp 2 2009-09-02 08:47:42Z joseph2002 $
/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

// Copyright Vladimir Prus 2002-2004.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/program_options/config.hpp>

#include <boost/program_options/errors.hpp>
#include <boost/program_options/detail/convert.hpp>
#include <boost/throw_exception.hpp>

#include <iostream>
#include <fstream>
#include <cassert>

#include "config_file.hpp"

using namespace std;
namespace po = boost::program_options;

xpt_common_config_file_iterator::xpt_common_config_file_iterator(
        const set<string>& allowed_options,
        bool allow_unregistered) :
    allowed_options(allowed_options),
    m_allow_unregistered(allow_unregistered)
{
    for (set<string>::const_iterator i = allowed_options.begin();
         i != allowed_options.end();
         ++i)
    {
        add_option(i->c_str());
    }
}

void xpt_common_config_file_iterator::add_option(const char* name)
{
    string s(name);
    assert(!s.empty());
    if (*s.rbegin() == '*') {
        s.resize(s.size()-1);
        bool bad_prefixes(false);
        // If 's' is a prefix of one of allowed suffix, then
        // lower_bound will return that element.
        // If some element is prefix of 's', then lower_bound will
        // return the next element.
        set<string>::iterator i = allowed_prefixes.lower_bound(s);
        if (i != allowed_prefixes.end()) {
            if (i->find(s) == 0)
                bad_prefixes = true;
        }
        if (i != allowed_prefixes.begin()) {
            --i;
            if (s.find(*i) == 0)
                bad_prefixes = true;
        }
        if (bad_prefixes)
            boost::throw_exception(po::error("bad prefixes"));
        allowed_prefixes.insert(s);
    }
}

namespace {
    string trim_ws(const string& s)
    {
        string::size_type n, n2;
        n = s.find_first_not_of(" \t\r\n");
        if (n == string::npos)
            return string();
        else {
            n2 = s.find_last_not_of(" \t\r\n");
            return s.substr(n, n2-n+1);
        }
    }
}

void xpt_common_config_file_iterator::get()
{
    string s;
    string::size_type n;
    bool found = false;

    while(this->getline(s)) {

        // strip '#' comments and whitespace
        if ((n = s.find('#')) != string::npos)
            s = s.substr(0, n);
        s = trim_ws(s);

        if (!s.empty()) {
            // Handle section name
            if (*s.begin() == '[' && *s.rbegin() == ']') {
                m_prefix = s.substr(1, s.size()-2);
                // XXX Commented
                //if (*m_prefix.rbegin() != '.')
                //    m_prefix += '.';
            }
            else if ((n = s.find('=')) != string::npos) {
                // XXX Added
                if (!m_prefix.empty() && *m_prefix.rbegin() != '.')
                    m_prefix += '.';

                string name = m_prefix + trim_ws(s.substr(0, n));
                string value = trim_ws(s.substr(n+1));

                bool registered = allowed_option(name);
                if (!registered && !m_allow_unregistered)
                    boost::throw_exception(po::unknown_option(name));

                if (value.empty())
                    boost::throw_exception(po::invalid_syntax(s, "no value given"));

                found = true;
                this->value().string_key = name;
                this->value().value.clear();
                this->value().value.push_back(value);
                this->value().unregistered = !registered;
                break;

            } else {
                // XXX Added
                if (!m_prefix.empty()) {
                    string name = m_prefix;
                    string value = s;

                    bool registered = allowed_option(name);
                    if (!registered && !m_allow_unregistered)
                        boost::throw_exception(po::unknown_option(name));

                    if (value.empty())
                        boost::throw_exception(po::invalid_syntax(s, "no value given"));

                    found = true;
                    this->value().string_key = name;
                    this->value().value.clear();
                    this->value().value.push_back(value);
                    this->value().unregistered = !registered;
                    break;
                } else
                    boost::throw_exception(po::invalid_syntax(s, "unrecognized line"));
            }
        }
    }
    if (!found)
        found_eof();
}

bool xpt_common_config_file_iterator::allowed_option(const string& s) const
{
    set<string>::const_iterator i = allowed_options.find(s);
    if (i != allowed_options.end())
        return true;
    // If s is "pa" where "p" is allowed prefix then
    // lower_bound should find the element after "p".
    // This depends on 'allowed_prefixes' invariant.
    i = allowed_prefixes.lower_bound(s);
    if (i != allowed_prefixes.begin() && s.find(*--i) == 0)
        return true;
    return false;
}

#if BOOST_WORKAROUND(__COMO_VERSION__, BOOST_TESTED_AT(4303)) || \
        (defined(__sgi) && BOOST_WORKAROUND(_COMPILER_VERSION, BOOST_TESTED_AT(741)))
template <>
bool basic_config_file_iterator<wchar_t>::getline(string& s)
{
    wstring ws;
    // On Comeau, using two-argument version causes
    // call to some internal function with std::wstring, and '\n'
    // (not L'\n') and compile can't resolve that call.

    if (std::getline(*is, ws, L'\n')) {
        s = boost::to_utf8(ws);
        return true;
    } else {
        return false;
    }
}
#endif


// xpt_parse_config_file
template <typename CharT>
po::basic_parsed_options<CharT>
xpt_parse_config_file(
        basic_istream<CharT>& is,
        const po::options_description& desc,
        bool allow_unregistered)
{
    set<string> allowed_options;

    const vector<boost::shared_ptr<po::option_description> >& options = desc.options();
    for (unsigned i = 0; i < options.size(); ++i)
    {
        const po::option_description& d = *options[i];

        if (d.long_name().empty())
            boost::throw_exception(
                po::error("long name required for config file"));

        allowed_options.insert(d.long_name());
    }

    // Parser return char strings
    po::parsed_options result(&desc);
    copy(xpt_basic_config_file_iterator<CharT>(
             is, allowed_options, allow_unregistered),
         xpt_basic_config_file_iterator<CharT>(),
         back_inserter(result.options));
    // Convert char strings into desired type.
    return po::basic_parsed_options<CharT>(result);
}

template
po::basic_parsed_options<char>
xpt_parse_config_file(
        basic_istream<char>& is,
        const po::options_description& desc,
        bool allow_unregistered);

#ifndef BOOST_NO_STD_WSTRING
template
po::basic_parsed_options<wchar_t>
xpt_parse_config_file(
        basic_istream<wchar_t>& is,
        const po::options_description& desc,
        bool allow_unregistered);
#endif
